/*************************************************************************************
 * Nombre del archivo: Proceso.java
 * Universidad del Valle de Guatemala
 * Curso: Sistemas Operativos
 * @author martin
 *          Martín Guzmán 08041
 * Fecha: Febrero de 2010
 * Descripción:
*************************************************************************************/

import java.io.BufferedReader;
import java.io.File;
import java.io.*;
import java.util.Hashtable;
import java.util.LinkedList;

import java.util.HashMap;
import java.util.Iterator;
import java.util.logging.Level;
import java.util.logging.Logger;

public class Proceso {

    private LinkedList<String> instrucciones = new LinkedList<String>(); //
    private int retorno = 0; //retorno del programa (después del END)
    private boolean is_end=false; //para ver si ya se terminó la interpretación
    private File archivo; //archivo que con el archivo a interpretar
    private Integer IP = 0; //Instruction Pointer
    private boolean proceso_correcto = true;
    private Integer estado;
    private boolean sincrono = true; //para ver si el proceso es modo síncrono o modo asíncrono
    private HashMap Registros = new HashMap();
    private String Nombre_archivo_destino=""; //path para generar un archivo físico una tabla de símbolos
    private String archivo_destino = "";

    private Integer process_ID;//process ID de este proceso
    private Integer PID_padre;//process ID del proceso padre
    private static int contador_ID = 0;

    private HashMap Tabla_simbolos = new HashMap();

    private HashMap Conjunto_Instrucciones = new HashMap();
    private HashMap System_calls = new HashMap();

    private File outputFile;
    private FileWriter out;
    private int hijos = 0;

    /***********************************************
     * Método: Proceso
     * Uso: Constructor
     * @param archivo contiene el archivo del que se hace instancia
     * @param cadena_de_llamada contiene el comando con el que es llamada
     * @return void
    ***********************************************/
    public Proceso(LinkedList<String> lista, String[] cadena_de_llamada, File archivo){

        this.process_ID = contador_ID++;
        this.escribir_ps_log("Building process: "+archivo.getPath()+" Process ID: "+this.process_ID+" Synchronous: "+this.sincrono);
        Init(lista, cadena_de_llamada, archivo);
        
    }

    public void Init(LinkedList<String> lista, String[] cadena_de_llamada, File archivo){

        /*  StateId para los estados del proceso:
        *  1 - New: Cuando el proceso está siendo creado o está esperando que se le asigne memoria.
        *  2 - Ready: Cuando el proceso está listo para ejecutarse.
        *  3 - Running: Cuando el proceso está asignado al procesador.
        *  4 - Waiting: Cuando el proceso está esperando alguna solicitud de I/O.
        *  5 - Terminated: Cuando un proceso hijo ha terminado, pero su proceso padre todavía está activo.
        */

        //this.estado=1;//New
        setEstado(1);
        //this.estado=2;//Ready
        //this.estado=3;//Running
        //this.estado=4;//Waiting
        //this.estado=5;//Terminated

        Conjunto_Instrucciones.put("set",new Integer(1));
        Conjunto_Instrucciones.put("add",new Integer(2));
        Conjunto_Instrucciones.put("sub",new Integer(3));
        Conjunto_Instrucciones.put("mul",new Integer(4));
        Conjunto_Instrucciones.put("div",new Integer(5));
        Conjunto_Instrucciones.put("jmp",new Integer(6));

        System_calls.put("echo",new Integer(7));
        System_calls.put("openfile",new Integer(8));
        System_calls.put("closefile",new Integer(9));
        System_calls.put("readline",new Integer(10));
        System_calls.put("sleep",new Integer(11));
        System_calls.put("end",new Integer(12));
        System_calls.put("fork",new Integer(13));
        System_calls.put("getstate",new Integer(14));

        this.archivo = new File(archivo.getPath());

        Asignar_Registros(cadena_de_llamada);
        Asignar_Instrucciones(lista);
        this.IP=0;
        
        this.PID_padre=null;

    }

    public Proceso(LinkedList<String> lista, String[] cadena_de_llamada, File archivo, String archivo_destino){
        try {
            this.process_ID = contador_ID++;
            this.archivo_destino=archivo_destino;
            Nombre_archivo_destino = archivo.getPath().substring(0, archivo.getPath().lastIndexOf("/") + 1) + archivo_destino;

            this.escribir_ps_log("Building process: "+archivo.getPath()+" Process ID: "+this.process_ID+" Synchronous: "+this.sincrono+" Destination File: "+Nombre_archivo_destino);

            Init(lista, cadena_de_llamada, archivo);
            this.sincrono = false;
            //System.out.println("." + Nombre_archivo_destino + ".");
            outputFile = new File(Nombre_archivo_destino);
            out = new FileWriter(outputFile);

        } catch (IOException ex) {
            this.Imprimir_errores("error en el archivo para guardar el proceso asíncronamente\n");
        }
    }


    /***********************************************
     * Método: proceso_correcto
     * Uso: para saber desde otro lado si el proceso
     *      está inicializado correctamente
     * @return booleano que dice si el proceso se inicializó correctamente
    ***********************************************/
    public boolean proceso_correcto(){
        return this.proceso_correcto;
    }

    /***********************************************
     * Método: getPadre
     * Uso: Obtiene el process ID del proceso padre
     * @return Integer con el PID del proceso padre. Null en caso no tenga proceso padre
    ***********************************************/
    public Integer getPadre(){
        return this.PID_padre;
    }

    /***********************************************
     * Método: setPadre
     * Uso:
     * @param
     * @return void
    ***********************************************/
    private void setPadre(int num){
        this.PID_padre = num;
    }

    /***********************************************
     * Método: es_sincrono
     * Uso: si este proceso corre síncrona o asíncrona mente
     * @return booleano el modo de ejecución del proceso
    ***********************************************/
    public boolean es_sincrono(){
        return this.sincrono;
    }

    /***********************************************
     * Método: Asignar_Registros
     * Uso: setea los 8 registros en su correspondiente valor
     * @param cadena_de_llamada contiene los parámetros enviados
     * @return void
    ***********************************************/
    private void Asignar_Registros(String[] cadena_de_llamada) {

        Integer[] registros = new Integer[8];
        //setea en 0 todos los registros
        for(int i=0;i<registros.length;i++){
            registros[i]=0;
        }
        //si se mandó al menos 1 parámetro
        if(cadena_de_llamada.length>1){
            try{
                for(int i=1;i<cadena_de_llamada.length;i++){
                    registros[i-1]=Integer.parseInt(cadena_de_llamada[i]);
                }
            }catch(Exception exp){
                this.Imprimir_errores("Valor no numérico mandado como parámetro\n");
                this.proceso_correcto = false;
                this.retorno=-1;//definir
            }
        }
        Registros.put("r0",registros[0]);
        Registros.put("r1",registros[1]);
        Registros.put("r2",registros[2]);
        Registros.put("r3",registros[3]);
        Registros.put("r4",registros[4]);
        Registros.put("r5",registros[5]);
        Registros.put("r6",registros[6]);
        Registros.put("r7",registros[7]);

    }

    /***********************************************
     * Método: Asignar_Instrucciones
     * Uso: setea las instrucciones según el contenido del archivo
     * @param lista contiene las líneas del archivo de texto
     * @return void
    ***********************************************/
    private void Asignar_Instrucciones(LinkedList<String> lista) {
        //borra todas las instrucciones que esten (por si acaso)
        instrucciones.clear();
        //asigna las instrucciones que contiene el archivo
        for(int i=0;i<lista.size();i++){
            instrucciones.add(lista.get(i));
        }
        Quitar_comentarios();
        Generar_tabla_simbolos();

    }

    /***********************************************
     * Método: quitar_comentarios
     * Uso: quita los comentarios de las instrucciones
     * @return void
    ***********************************************/
    private void Quitar_comentarios() {
        try{
            //quita las instrucciones que empiezan con ;
            for(int i=0;i<this.instrucciones.size();i++){
                if(instrucciones.get(i).startsWith(";")){
                    instrucciones.remove(i);
                    instrucciones.add(i,"");
                }
            }
            //borra todo lo que sigue a ;
            for(int i=0;i<this.instrucciones.size();i++){
                if(instrucciones.get(i).contains(";")){
                    int b = instrucciones.get(i).indexOf(";");
                    String temp = instrucciones.get(i).substring(0,b);
                    instrucciones.remove(i);
                    instrucciones.add(i,temp);
                }
            }
            //borra todos los espacios ENTER'S
            for(int i=0;i<this.instrucciones.size();i++){
                if(this.instrucciones.get(i).compareTo("")==0){
                    this.instrucciones.remove(i--);
                }
            }

            Quitar_espacios();

        }catch(Exception exp){
            this.Imprimir_errores("Error al quitar los comentarios..\n");
            this.proceso_correcto = false;
        }


    }

    /***********************************************
     * Método: Quitar espacios
     * Uso: quita espacios inservibles de las instrucciones
     * @return void
    ***********************************************/
    private void Quitar_espacios(){

        for(int i=0;i<this.instrucciones.size();i++){
            String temp = Quitar_espacios(instrucciones.get(i));
            instrucciones.remove(i);
            instrucciones.add(i, temp);
        }
    }

    /***********************************************
     * Método: Quitar espacios
     * Uso: quita espacios inservibles
     * @return cadena sin espacios inservibles
     *          y separando los átomos por
     *         espacios
    ***********************************************/
    private String Quitar_espacios(String instruccion){
        String retorno=instruccion;
        //por si hay tabulaciones

        while(retorno.contains("  ")){
            retorno=retorno.replaceAll("  "," ");
        }
        //por si inicia con un espacio
        if(retorno.startsWith(" ")){
            retorno = retorno.substring(1);
        }
        //quitar el espacio al final
        if(retorno.endsWith(" ")){
            retorno = retorno.substring(0, retorno.length()-1);
        }
        if(retorno.length()==0){
            return "";
        }
        return retorno;
    }

    /***********************************************
     * Método: getPID
     * Uso: obtiene el identificador del proceso
     * @return Integer con el identificador
    ***********************************************/
    public Integer getPID(){
        return this.process_ID;
    }

    /***********************************************
     * Método: getEstado
     * Uso: devuelve el valor en que está el estado
     * @return Integer con el estado
    ***********************************************/
    public Integer getEstado(){
        return this.estado;
    }

    /***********************************************
     * Método: setEstado
     * Uso: setear el estado
     * @return void
    ***********************************************/
    public void setEstado(Integer num){
        if(this.estado!=num){
            this.estado=num;
            this.escribir_ps_log("\tState change: "+this.get_Nombre_Estado()+" Process ID: "+this.process_ID);
        }

    }

    /***********************************************
     * Método: getEstado
     * Uso: devuelve el valor en que está el estado
     * @return Integer con el estado
    ***********************************************/
    public String get_Nombre_Estado(){
        if(this.estado==1)
            return "New";
        if(this.estado==2)
            return "Ready";
        if(this.estado==3)
            return "Running";
        if(this.estado==4)
            return "Waiting";
        if(this.estado==5)
            return "Terminated";
        if(this.estado==6)
            return "Killed";
        return "";
    }

    /***********************************************
     * Método: Generar_tabla_simbolos
     * Uso: genera la tabla de símbolos con las etiquetas
     * @return void
    ***********************************************/
    private void Generar_tabla_simbolos() {
        //Nombre_tabla_simbolos = archivo.getPath().substring(0,archivo.getPath().lastIndexOf("/")+1)+"#"+archivo.getPath().substring(archivo.getPath().lastIndexOf("/")+1)+"_tabla_simbolos";

        LinkedList<String> etiquetas = new LinkedList<String>();
        LinkedList<Integer> linea_de_etiqueta = new LinkedList<Integer>();


        for(int i=0;i<this.instrucciones.size();i++){
            String temp = instrucciones.get(i);

            //en esa linea solo hay etiqueta
            if(temp.startsWith("\'") && temp.endsWith("\'")){
                String temp4 = temp.substring(1, temp.length()-1);
                if(temp4.contains(" ")==false){
                    etiquetas.add(temp4);
                    linea_de_etiqueta.add(i);
                    //quita la instruccion con la etiqueta y quita 1 al contador
                    instrucciones.remove(i--);
                }
                else{
                    this.Imprimir_errores("Etiqueta es inválida porque tiene espacios en blanco\n");
                    this.proceso_correcto = false;
                }
            }
            //en esa linea hay etiqueta y no solo eso hay
            else if(temp.startsWith("\'") && temp.lastIndexOf("\'")!=0){

                String temp4 = temp.substring(1, temp.lastIndexOf("\'"));
                if (temp4.contains(" ")==false){
                    //significa que hay una etiqueta seguida de la instrucción
                    etiquetas.add(temp4);
                    linea_de_etiqueta.add(i);
                    //toca borrar etiqueta de esa línea
                    String temp1 = temp.substring(temp.lastIndexOf("\'")+1);
                    //temp1 contiene la línea
                    String temp2 = Quitar_espacios(temp1);
                    instrucciones.remove(i);
                    instrucciones.add(i, temp2);
                }
                else{
                    this.Imprimir_errores("Etiqueta es inválida porque tiene espacios en blanco\n");
                    this.proceso_correcto = false;
                }

            }

        }
        //*********************************************
        // guarda en el hashMap la tabla de símbolos
        for(int i=0;i<etiquetas.size();i++){
            this.Tabla_simbolos.put(etiquetas.get(i).toLowerCase(),linea_de_etiqueta.get(i));
        }
        //*********************************************


    }

    /***********************************************
     * Método: Finalizar_Proceso
     * Uso: cambia de estado y activa bandera para salir del proceso
     * @param estado para setear el estado
     * @param is_end para setear la bandera
     * @return void
    ***********************************************/
    public void Finalizar_Proceso(int estado, boolean is_end, int error){
        
        this.is_end=is_end;
        setEstado(estado);
        this.escribir_ps_log("Completion of process: "+this.archivo.getPath()+" Process ID: "+this.process_ID);

        if(error<0){
            this.retorno=error;
        }
        if(!sincrono){
            try {
                this.out.close();
            } catch (Exception ex) {
                this.Imprimir_errores("error al cerrar el archivo");
            }
        }
    }

    /***********************************************
     * Método: Ejecutar_instruccion
     * Uso: ejecuta la instruccion dada por la línea
     * @param linea_a_ejecutar
     * @return void
    ***********************************************/
    public boolean Exec(){
        
        if(this.IP==this.instrucciones.size()){
            this.Finalizar_Proceso(5, true,-2);//terminated //sin end
            //Significa que el proceso no tenía un end...
            return true;
        }

        else if(!is_end){
            //this.estado=3;//Running
            setEstado(3);
            Ejecutar_instruccion();
            return false;
        }
        else{
            this.Finalizar_Proceso(5, true,0);//terminated
            return true;
        }
    }

    /***********************************************
     * Método: Ejecutar_instruccion
     * Uso: ejecuta la instruccion dada por la línea
     * @param linea_a_ejecutar
     * @return void
    ***********************************************/
    private void Ejecutar_instruccion() {
        //ejecuta la lína que contiene linea_a_ejecutar de instrucciones

        String Instruccion_Actual = this.instrucciones.get(IP); //la instrucción a ejecutar

        Integer comando_a_ejecutar=-1;

        //acción a ejecutar
        String temp = Instruccion_Actual.split(" ")[0];
        String[] temp1 = Instruccion_Actual.split(" ");
        if(this.Conjunto_Instrucciones.containsKey(temp.toLowerCase())){
            comando_a_ejecutar = (Integer)this.Conjunto_Instrucciones.get(temp.toLowerCase());
        }
        else if(this.System_calls.containsKey(temp.toLowerCase()))
        {
            comando_a_ejecutar = (Integer)this.System_calls.get(temp.toLowerCase());
        }
        else{
            //System.out.println(Instruccion_Actual);
            this.Imprimir_errores("Instrucción no válida\n");
            this.Finalizar_Proceso(5, true,-2);//terminated //instrucción no válida
        }

        Comando_a_ejecutar(comando_a_ejecutar, Instruccion_Actual);

    }

    /***********************************************
     * Método: Comando_a_ejecutar
     * Uso: mandar a llamar a la función respectiva
     * @param comando_a_ejecutar contiene la acción a ejecutar
     * @param temp1 contiene la cadena separada
     * @return void
    ***********************************************/
    private void Comando_a_ejecutar(Integer comando_a_ejecutar, String instruccion) {

        //instrucción contiene la instrucción completa
        //temp1 contiene la separación por espacios
        String temp1[] = instruccion.split(" ");

        switch (comando_a_ejecutar){
            case 1: //********************************** set **********************************
                //System.out.println("set");
                //solo puede tener 3 parametros
                if(temp1.length==3){
                    Set(temp1[1],temp1[2]);
                }
                else{
                    this.Imprimir_errores("cantidad de parámetros incorrecto en un set\n");
                    this.Finalizar_Proceso(5, true,-1);//terminated //parámetro incorrecto
                }
                //para ejecutar la siguiente instrucción
                this.IP++;
                break;
            case 2: //********************************** add **********************************
                //System.out.println("add");
                //solo puede tener 4 parametros
                if(temp1.length==4){
                    Add(temp1[1],temp1[2],temp1[3]);
                }
                else{
                    this.Imprimir_errores("cantidad de parámetros incorrecto en un add\n");
                    this.Finalizar_Proceso(5, true,-1);//terminated //parámetro incorrecto
                }
                //para ejecutar la siguiente instrucción
                this.IP++;
                break;
            case 3: //********************************** sub **********************************
                //System.out.println("sub");
                //solo puede tener 4 parametros
                if(temp1.length==4){
                    Sub(temp1[1],temp1[2],temp1[3]);
                }
                else{
                    this.Imprimir_errores("cantidad de parámetros incorrecto en un sub\n");
                    this.Finalizar_Proceso(5, true,-1);//terminated //parámetro incorrecto
                }
                //para ejecutar la siguiente instrucción
                this.IP++;
                break;
            case 4: //********************************** mul **********************************
                //System.out.println("mul");
                //solo puede tener 4 parametros
                if(temp1.length==4){
                    Mul(temp1[1],temp1[2],temp1[3]);
                }
                else{
                    this.Imprimir_errores("cantidad de parámetros incorrecto en un mul\n");
                    this.Finalizar_Proceso(5, true,-1);//terminated //parámetro incorrecto
                }
                //para ejecutar la siguiente instrucción
                this.IP++;
                break;
            case 5: //********************************** div **********************************
                //System.out.println("div");
                //solo puede tener 5 parametros
                if(temp1.length==5){
                    Div(temp1[1],temp1[2],temp1[3],temp1[4]);
                }
                else{
                    this.Imprimir_errores("cantidad de parámetros incorrecto en un div\n");
                    this.Finalizar_Proceso(5, true,-1);//terminated //parámetro incorrecto
                }
                //para ejecutar la siguiente instrucción
                this.IP++;
                break;
            case 6: //********************************** jmp **********************************
                //solo puede tener 3 parametros
                //System.out.println("jmp");
                if(temp1.length==3){
                    Jmp(temp1[1],temp1[2]);
                }
                else{
                    this.Imprimir_errores("cantidad de parámetros incorrecto en un jmp\n");
                    this.Finalizar_Proceso(5, true,-1);//terminated //parámetro incorrecto
                }
                break;
            case 7: //********************************** echo **********************************
                //System.out.println("echo");
                if(instruccion.length()>=5){
                    String string=instruccion.substring(5);
                    Echo(string);
                }
                else{
                    this.Imprimir_errores("cantidad de parámetros incorrecto en un echo\n");
                    this.Finalizar_Proceso(5, true,-1);//terminated //parámetro incorrecto
                }
                //para ejecutar la siguiente instrucción
                this.IP++;
                break;
            case 8: //********************************** openfile **********************************
                //System.out.println("openfile");
                String nombre_archivo="",reg="";
                try{
                    nombre_archivo = instruccion.substring(instruccion.indexOf("\""),instruccion.lastIndexOf("\"")+1);
                }catch(Exception exp)
                {}
                reg = temp1[temp1.length-1];
                OpenFile(nombre_archivo,reg);
                
                break;
            case 9: //********************************** closefile **********************************
                //System.out.println("closefile");
                //para ejecutar la siguiente instrucción
                if(temp1.length==2){

                    CloseFile(temp1[1]);
                }
                else{
                    this.Imprimir_errores("cantidad incorrecta de parámetros en un closeFile\n");
                    this.Finalizar_Proceso(5, true,-1);//terminated //parámetro incorrecto
                }
                this.IP++;

                break;
            case 10: //********************************** readline **********************************
                //System.out.println("readline");
                if(temp1.length==3){

                    ReadLine(temp1[1],temp1[2]);//para ejecutar la siguiente instrucción
                }
                else{
                    this.Imprimir_errores("cantidad incorrecta de parámetros en un closeFile\n");
                    this.Finalizar_Proceso(5, true,-1);//terminated //parámetro incorrecto
                }
                this.IP++;
                break;
            case 11: //********************************** sleep **********************************
                //System.out.println("sleep");
                if(temp1.length==1){
                    Sleep();
                    this.IP++;
                }
                else{
                    this.Finalizar_Proceso(5, true,-1);//terminated //parámetro incorrecto
                    this.Imprimir_errores("Cantidad de parámetros incorrecto en el sleep\n");
                }
                
                break;
            case 12: //********************************** end **********************************
                if(temp1.length==2){
                    End(temp1[1]);
                }
                else{
                    this.Imprimir_errores("Cantidad de parámetros incorrecto en el end\n");
                    this.Finalizar_Proceso(5, true,-1);//terminated //parámetro incorrecto
                }
                        //System.out.println("end");
                break;
            case 13: //********************************** fork **********************************
                //System.out.println("fork");
                String temp="";
                temp=instruccion.substring(instruccion.indexOf("\""),instruccion.lastIndexOf("\"")+1);
                //System.out.println(temp);
                if(temp.startsWith("\"") && temp.endsWith("\"")){
                    //para ejecutar la siguiente instrucción
                    Fork(temp,temp1[temp1.length-1]);
                    this.IP++;
                }
                else{
                    this.Finalizar_Proceso(5, true,-1);//terminated //parámetro incorrecto
                    this.Imprimir_errores("parámetro incorrecto en Fork\n");
                }
                break;
            case 14: //********************************** getstate **********************************
                //System.out.println("getstate");
                String valor="",reg2="",reg1="";
                if(temp1.length==4){
                    valor=temp1[1];
                    reg2=temp1[2];
                    reg1=temp1[3];

                    getState(valor,reg2,reg1);
                    //para ejecutar la siguiente instrucción
                    this.IP++;
                }
                else{
                    this.Finalizar_Proceso(5, true,-1);//terminated //parámetro incorrecto
                    this.Imprimir_errores("Cantidad incorrecta de parámetros\n");
                }

                break;
        }
    }

    /***********************************************
     * Método: Revisar_si_es_registro
     * Uso: revisa si se encuentra entre los registros
     * @param string revisa si esta string entre los registros
     * @return boolean retorna un booleano diciendo si está o no
    ***********************************************/
    private boolean Revisar_si_es_registro(String string){
        if(this.Registros.containsKey(string.toLowerCase()) ){
            return true;
        }
        else
            return false;
    }

    /***********************************************
     * Método: Obtener_valor_registro
     * Uso: devuelve el valor del registro
     * @param string revisa si esta string entre los registros
     * @return Integer valor que contiene el registro
    ***********************************************/
    private Integer Obtener_valor_registro(String string){
        if(this.Registros.containsKey(string.toLowerCase()) ){
            return (Integer)this.Registros.get(string.toLowerCase());
        }
        else
            return null;
    }

    /***********************************************
     * Método: Obtener_valor_etiqueta
     * Uso: devuelve el valor de una etiqueta
     * @param string revisa si esta string entre las etiquetas
     * @return Integer valor que contiene la etiqueta
    ***********************************************/
    private Integer Obtener_valor_etiqueta(String string){
        if(this.Tabla_simbolos.containsKey(string.toLowerCase()) ){
            return (Integer)this.Tabla_simbolos.get(string.toLowerCase());
        }
        else
            return null;
    }


    /***********************************************
     * Método: Revisar_si_es_etiqueta
     * Uso: revisa si se encuentra entre las etiquetas
     * @param string revisa si esta string entre las etiquetas
     * @return boolean retorna un booleano diciendo si está o no
    ***********************************************/
    private boolean Revisar_si_es_etiqueta(String string){
        if(this.Tabla_simbolos.containsKey(string.toLowerCase()) ){
            return true;
        }
        else
            return false;
    }

    /***********************************************
     * Método: Set
     * Uso: set reg valor o set reg1 reg2.
     *      Ejemplo: set r1 0.
     *      Establece el valor especificado en el registro.
     * @param reg registro destino
     * @param valor registro o valor fuente
     * @return void
    ***********************************************/
    private void Set(String reg, String valor) {
        //revisa si el fuente está entre los registros
        if(Revisar_si_es_registro(reg)){
            //se sabe que se encuentra el registro destino

            //si el valor es un registro
            if(Revisar_si_es_registro(valor)){
                this.Registros.put(reg.toLowerCase(), (Integer)Registros.get(valor.toLowerCase()) );
            }
            //el valor es un número directamnte
            else{
                try{
                    this.Registros.put(reg.toLowerCase(), Integer.parseInt(valor) );
                }catch(Exception exp){
                    this.Imprimir_errores("se está tratando de setear algo que no es un número...\n");
                    this.Finalizar_Proceso(5, true,-6);//terminated //valor no numérico
                }
            }
        }
        //de lo contrario es error porque no hay a donde enviarlo...
        else{
            this.Imprimir_errores("Destino incorrecto en el set\n");
            this.Finalizar_Proceso(5, true,-7);//terminated //registro no encontrado
        }
    }

    /***********************************************
     * Método: Add
     * Uso: Add reg1 reg2 reg3.
     *      Equivalente a reg1 = reg2 + reg3.
     *      Ejemplo: add r1 r2 r3.
     * @param destino registro destino
     * @param fuente1 registro fuente1
     * @param fuente2 registro fuente2
     * @return void
    ***********************************************/
    private void Add(String destino, String fuente1, String fuente2) {
        //revisa que destino, fuente1 y fuente2 sean registros
        if(Revisar_si_es_registro(destino) && Revisar_si_es_registro(fuente1) && Revisar_si_es_registro(fuente2)){
            try{
                this.Registros.put(destino.toLowerCase(), (Obtener_valor_registro(fuente1)+Obtener_valor_registro(fuente2)));
            }catch(Exception exp){
                this.Imprimir_errores("los registros no tienen valores numéricos\n");
                this.Finalizar_Proceso(5, true,-6);//terminated //parámetro incorrecto
            }
        }
        //de lo contrario es error porque no hay a donde enviarlo...
        else{
            this.Imprimir_errores("No se utilizaron registros en el add\n");
            this.Finalizar_Proceso(5, true,-7);//terminated //registro no encontrado
        }
    }

    /***********************************************
     * Método: Sub
     * Uso: Sub reg1 reg2 reg3.
     *      Equivalente a reg1 = reg2 - reg3.
     * @param destino registro destino
     * @param fuente1 registro fuente1
     * @param fuente2 registro fuente2
     * @return void
    ***********************************************/
    private void Sub(String destino, String fuente1, String fuente2) {
        //revisa que destino, fuente1 y fuente2 sean registros
        if(Revisar_si_es_registro(destino) && Revisar_si_es_registro(fuente1) && Revisar_si_es_registro(fuente2)){
            try{
                this.Registros.put(destino.toLowerCase(), (Obtener_valor_registro(fuente1)-Obtener_valor_registro(fuente2)));
            }catch(Exception exp){
                this.Imprimir_errores("los registros no tienen valores numéricos\n");
                this.Finalizar_Proceso(5, true,-6);//terminated //valores no numéricos
            }
        }
        //de lo contrario es error porque no hay a donde enviarlo...
        else{
            this.Imprimir_errores("No se utilizaron registros en el sub\n");
            this.Finalizar_Proceso(5, true,-7);//terminated //registro no encontrado
        }
    }

    /***********************************************
     * Método: Mul
     * Uso: Mul reg1 reg2 reg3.
     *      Equivalente a reg1 = reg2 * reg3.
     * @param destino registro destino
     * @param fuente1 registro fuente1
     * @param fuente2 registro fuente2
     * @return void
    ***********************************************/
    private void Mul(String destino, String fuente1, String fuente2) {
        //revisa que destino, fuente1 y fuente2 sean registros
        if(Revisar_si_es_registro(destino) && Revisar_si_es_registro(fuente1) && Revisar_si_es_registro(fuente2)){
            try{
                this.Registros.put(destino.toLowerCase(), (Obtener_valor_registro(fuente1)*Obtener_valor_registro(fuente2)));
            }catch(Exception exp){
                this.Imprimir_errores("los registros no tienen valores numéricos\n");
                this.Finalizar_Proceso(5, true,-6);//terminated //valores no numéricos
            }
        }
        //de lo contrario es error porque no hay a donde enviarlo...
        else{
            this.Imprimir_errores("No se utilizaron registros en el mul\n");
            this.Finalizar_Proceso(5, true,-7);//terminated //registro no encontrado
        }
    }

    /***********************************************
     * Método: Div
     * Uso: Div reg1 reg2 reg3 reg4.
     *      Equivalente a la división entera de reg1 = reg2 / reg3.
     *      reg4 contiene el residuo. En el caso que se efectúe una
     *      división en 0, el programa debe terminar con resultado -2.
     * @param destino registro destino
     * @param fuente1 registro fuente1
     * @param fuente2 registro fuente2
     * @param residuo registro con el residuo
     * @return void
    ***********************************************/
    private void Div(String destino, String fuente1, String fuente2, String residuo) {
        //revisa que destino, fuente1 y fuente2 sean registros
        if(Revisar_si_es_registro(destino) && Revisar_si_es_registro(fuente1) && Revisar_si_es_registro(fuente2) && Revisar_si_es_registro(residuo)){
            try{
                //guarda el resultado
                Integer uno_temp = Obtener_valor_registro(fuente1);
                Integer dos_temp = Obtener_valor_registro(fuente2);
                this.Registros.put(destino.toLowerCase(), (uno_temp/dos_temp));
                //guarda el residuo
                this.Registros.put(residuo.toLowerCase(), (uno_temp%dos_temp));
            }catch(Exception exp){
                this.Imprimir_errores("división entre 0\n");
                this.Finalizar_Proceso(5, true,-2);//terminated //registro no encontrado
            }
        }
        //de lo contrario es error porque no hay a donde enviarlo...
        else{
            this.Imprimir_errores("No se utilizaron registros en el div\n");
            this.Finalizar_Proceso(5, true,-7);//terminated //registro no encontrado
        }
    }

    /***********************************************
     * Método: Jmp
     * Uso: Jmp reg lab. Si reg es verdadero
     *      (mayor que 0), salta a lab.
     *      No es necesario poner delimitar la etiqueta con comillas
            simples. En el caso que la etiqueta no exista, el
     *      programa debe terminar con resultado -1.
     * @param reg registro para evaluar la preposición (verdadero = reg>0)
     * @param etiqueta etiqueta a la cual hacer el salto
     * @return void
    ***********************************************/
    private void Jmp(String reg, String etiqueta_temp) {
        //revisa si la etiqueta está en la tabla de etiquetas
        if(this.Revisar_si_es_etiqueta(etiqueta_temp)){
            //revisa que sea un registro
            if(this.Revisar_si_es_registro(reg)){
                //revisa si la proposición es verdadera (reg>0)
                if(this.Obtener_valor_registro(reg)>0){
                    this.IP=Obtener_valor_etiqueta(etiqueta_temp);
                }
                else{
                    this.IP++;
                }
            }
            else{
                this.Imprimir_errores("Se está tratando de evaluar algo que no es un registro en el jmp\n");
            }
        }
        else{
            this.Imprimir_errores("No existe la etiqueta marcada en el jump\n");
            this.Finalizar_Proceso(5, true,-5);//terminated //etiqueta inválida
        }

    }

    /***********************************************
     * Método: Echo
     * Uso: Echo “string” o Echo reg.
     *      Despliega la cadena o el valor ascii del registro.
     * @param etiqueta etiqueta a la cual hacer el salto
     * @return void
    ***********************************************/
    private void Echo(String string) {
        String imprimir="";
        //System.out.println("Cadena entrante en echo: ."+string+"."); //TODO Testing
        if(this.Revisar_si_es_registro(string)){
                imprimir=""+this.Obtener_valor_registro(string);
        }
        else{
            if(string.startsWith("\"") && string.endsWith("\"") && string.compareTo("\"\"")!=0){
                imprimir=string.substring(1,string.length()-1);
                while(imprimir.contains("\\n")){
                    imprimir=imprimir.replace("\\n", "\n"); //reemplazar para que imprima ENTER'S
                }
            }
            else{
                this.Imprimir_errores("Un echo sin comillas o comillas vacías\n");
                this.Finalizar_Proceso(5, true,-2);//terminated //instrucción no encontrada
            }

        }
        this.Imprimir(imprimir);
    }

    /***********************************************
     * Método: Sleep
     * Uso: Cede procesador y pasa proceso a estado waiting
     * @return void
    ***********************************************/
    private void Sleep() {
        CPU.cambio_proceso=true;
        //this.estado=1;//New
        //this.estado=2;//Ready
        //this.estado=3;//Running
        //this.estado=4;//Waiting
        setEstado(4);
        //this.estado=5;//Terminated
    }

    /***********************************************
     * Método: Fork
     * Uso: Crea un proceso, la cadena especificada contiene
     *      el nombre del comando (system program o user program) y
     *      los parámetros. El proceso hereda el mecanismo de output
     *      (línea de comando o archivo). En el registro especificado
     *      se devuelve el pid del proceso creado.
     * @param etiqueta etiqueta a la cual hacer el salto
     * @return void
    ***********************************************/
    private void Fork(String instruccion_de_fork, String registro){

        String temp1 = instruccion_de_fork.substring(1,instruccion_de_fork.length()-1);

        //System.out.println("temp1: ."+temp1+".");
        if(!sincrono){
            temp1=temp1+" > "+(++hijos)+"_"+this.archivo_destino;
        }
        //System.out.println("temp1: ."+temp1+".");
        
        if(Shell.revisar_system_program(temp1) && this.Revisar_si_es_registro(registro)){
            //si está en directorio raiz significa que era un user program y que se creó el proceso...
            if(Shell.se_encuentra_en_directorio_raiz_y_es_archivo(temp1)){
                //asigna el padre al proceso creado
                CPU.Cola_Procesos.getLast().setPadre(this.process_ID);

                Integer valor_a_registro = CPU.Cola_Procesos.getLast().process_ID;
                //poner el PID del hijo en el registro asignado
                this.Registros.put(registro.toLowerCase(), valor_a_registro );
            }
        }
        else{
            this.Imprimir_errores("El fork da un error\n");
            this.Finalizar_Proceso(5, true,-1);//terminated //error en el fork
        }
    }

    /***********************************************
     * Método: getState
     * Uso: valor reg2 reg1 o GetState reg3 reg2 reg1. Obtiene el estado del proceso especificado (stateId) en reg2. Retorna el
      resultado (Si el proceso no ha terminado, devuelve 0) en reg1. Únicamente será posible obtener el estado de un proceso hijo.
     * @param valor process ID del proceso a verificar
     * @param reg1 al final contiene el valor del estado del proceso
     * @param reg2 al final contiene el resultado (retorno) del proceso
     * @return void
    ***********************************************/
    private void getState(String valor, String reg2, String reg1) {
        if(this.Revisar_si_es_registro(reg1) && this.Revisar_si_es_registro(reg2)){
            //revisa si el valor es un registro
                
            if(this.Revisar_si_es_registro(valor)){
                Integer temp_PID = this.Obtener_valor_registro(valor);
                Integer temp_retorno = CPU.Todos_Procesos.get(temp_PID).retorno;
                Integer temp_estado = CPU.Todos_Procesos.get(temp_PID).getEstado();

                //asignar al registro indicado el estado
                this.Registros.put(reg2.toLowerCase(), temp_estado );
                //asignar al registro indicado el retorno
                this.Registros.put(reg1.toLowerCase(), temp_retorno );
            }
            //valor es un número
            else{

                try{
                    Integer.parseInt(valor);

                }catch(Exception exp){
                    this.Finalizar_Proceso(5, true,-6);//terminated //algo no numérico
                }


            }
        }
    }

    /***********************************************
     * Método: OpenFile
     * Uso: Abre el archivo con el nombre especificado (Espera si el archivo está en uso).
     *      El registro devuelve el valor del handle (Identificador de archivo),
     *      0 si no fue posible abrir el archivo. El proceso debe pasar al estado waiting.
     * @param nombre_archivo contiene el path al archivo por abrir
     * @param reg contiene el registro donde se coloca el handle
     * @return void
    ***********************************************/
    private void OpenFile(String nombre_archivo, String reg) {
        if(this.Revisar_si_es_registro(reg)){
            //System.out.println("Opening file... :P! ."+nombre_archivo+". "+reg);
            String path_completo = archivo.getPath().substring(0,archivo.getPath().lastIndexOf("/")+1)+nombre_archivo.substring(1,nombre_archivo.length()-1);
            //System.out.println("."+path_completo+".");

            LinkedList<Archivo> cola = CPU.Archivos_PID.getCola();

            boolean ya_esta=false;

            //revisa si el archivo está siendo utilizado por otro proceso
            for(int i=0;i<cola.size();i++){
                if(cola.get(i).getPath().compareTo(path_completo)==0){
                    ya_esta=true;
                    break;
                }
            }
            //si todavía no hay ningún proceso utilizando ese archivo
            if(!ya_esta){
                Archivo archivo_temp = new Archivo(path_completo);
                CPU.Archivos_PID.add(archivo_temp);
                CPU.cambio_proceso=true;
                //this.estado=4;//Waiting
                setEstado(4);
                //para ejecutar la siguiente instrucción
                this.IP++;
                this.Registros.put(reg.toLowerCase(),archivo_temp.getHandler());
                //****************************************************************************************
            }
            else{
                //ya hay un proceso utilizándolo
                //se activa el contextSwitch (para cambiar de proceso) y esperar hasta que se deje de utilizar
                CPU.cambio_proceso=true;
                //this.estado=4;//Waiting
                setEstado(4);
            }
        }
        else{
            this.Imprimir_errores("El parámetro mandado al OpenFile no es un registro\n");
            this.Finalizar_Proceso(5, true,-1);//terminated //error en el openFile
        }

        //System.out.println("Cantidad de archivos utilizándose: "+CPU.Archivos_PID.getCola().size());
    }

    /***********************************************
     * Método: CloseFile
     * Uso: Cierra el archivo especificado. El proceso debe pasar al estado waiting.
     * @param reg contiene el handle del archivo a cerrar
     * @return void
    ***********************************************/
    private void CloseFile(String reg) {

        if(this.Revisar_si_es_registro(reg)){
            //obtiene el valor contenido en el registro
            int temp = this.Obtener_valor_registro(reg);
            LinkedList<Archivo> cola = CPU.Archivos_PID.getCola();
            for(int i=0;i<cola.size();i++){
                //System.out.println("Handler mandado a cerrar: "+temp+" revisando: "+cola.get(i).getHandler());
                if(cola.get(i).getHandler()==temp){
                    //cerrar este archivo
                    //System.out.println("prueba ***************************************************");
                    cola.get(i).closeFile();
                    cola.remove(i);
                    break;
                }
            }
            //this.estado=4;//Waiting
            setEstado(4);
            CPU.cambio_proceso=true;
            //System.out.println("Cantidad de archivos utilizándose: "+CPU.Archivos_PID.getCola().size());
        }
        else{
            this.Imprimir_errores("No se mandó un parámetro correcto. No es registro el que está en el closeFile\n");
            this.Finalizar_Proceso(5, true,-1);//terminated //parámetros incorrectos
        }
    }

    /***********************************************
     * Método: ReadLine
     * Uso: Lee una línea del archivo y lo imprime. Es decir,
     *      leer desde la posición actual hasta (inclusive) un carácter
     *      de nueva línea. Reg1 especifica el handle y reg2 devuelve el
     *      número de caracteres impresos. El proceso debe pasar al estado
     *      waiting.
     * @param reg1 contiene el handle del archivo a leer
     * @param reg2 contiene la cantidad de caracteres impresos
     * @return void
    ***********************************************/
    private void ReadLine(String reg1, String reg2) {
        //revisa si los registros son válidos
        if(this.Revisar_si_es_registro(reg1) && this.Revisar_si_es_registro(reg2)){
            //obtiene el valor contenido en el registro
            int temp = this.Obtener_valor_registro(reg1);
            LinkedList<Archivo> cola = CPU.Archivos_PID.getCola();

            String linea="";

            for(int i=0;i<cola.size();i++){
                if(cola.get(i)!=null && cola.get(i).getHandler()==temp){
                    //cerrar este archivo
                    linea = cola.get(i).readLine();
                    break;
                }
            }
            int cant_impresiones=0;
            try{
                cant_impresiones=linea.length();
            }catch(Exception exp){
                //entra a la exception si es null la línea
                linea = "";
                cant_impresiones=0;
            }
            //asigna el valor correspondiente al registro
            this.Registros.put(reg2.toLowerCase(),cant_impresiones);
            //imprime la linea que esta ejecutando
            this.Imprimir(linea);
            //System.out.println("ReadLine: ."+linea+". Tamaño: "+cant_impresiones);
            //this.estado=4;//Waiting
            setEstado(4);
            CPU.cambio_proceso=true;
        }
        else{
            this.Imprimir_errores("No se mandó un parámetro correcto. No es registro el que está en el ReadLine\n");
            this.Finalizar_Proceso(5, true,-1);//terminated //parámetros incorrectos
        }
    }

    /***********************************************
     * Método: End
     * Uso: End valor o End reg. Termina el proceso con el
     *      resultado especificado. Se utilizarán valores
     *      positivos para resultados exitosos y negativos para
     *      errores. 0 se utilizará para procesos no terminados
     *      en el caso de GetState
     * @param valor contiene el handle del archivo a leer
     * @return void
    ***********************************************/
    private void End(String valor) {
        //revisa si los registros son válidos
        if(this.Revisar_si_es_registro(valor)){
            this.retorno = this.Obtener_valor_registro(valor);
            this.is_end=true;
            //this.estado=5;//terminated
            setEstado(5);
            this.escribir_ps_log("Completion of process: "+this.archivo.getPath()+" Process ID: "+this.process_ID);
        }
        else{
            try{
                this.retorno = Integer.parseInt(valor);
                this.is_end=true;
                //this.estado=5;//terminated
                setEstado(5);
                this.escribir_ps_log("Completion of process: "+this.archivo.getPath()+" Process ID: "+this.process_ID);
            }catch(Exception exp){
                this.Imprimir_errores("No se mandó un parámetro correcto en el End\n");
                this.Finalizar_Proceso(5,true,-1);
            }
        }
    }

    /***********************************************
     * Método: Imprimir
     * Uso: decide si escribir en un archivo o en la terminal
     *      dependiendo de si es síncrono o no
     * @param imprimir contiene lo que se desea imprimir
     * @return void
    ***********************************************/
    private void Imprimir(String imprimir) {
        
        //si es síncrona se imprime en la terminal
        if(this.sincrono){
            System.out.print(imprimir);
        }
        //si no es síncrona se imprime en el archivo
        else{
            escribir_archivo(imprimir);
        }
    }

    /***********************************************
     * Método: Imprimir_errores
     * Uso: decide si escribir en un archivo o en la terminal
     *      dependiendo de si es síncrono o no
     * @param imprimir contiene lo que se desea imprimir
     * @return void
    ***********************************************/
    private void Imprimir_errores(String imprimir) {
        if(Shell.debug_mode){
            Imprimir_debug(imprimir);
        }
        //System.out.print(imprimir);
    }

    private void Imprimir_debug(String imprimir) {
        if(Shell.sincrono_debug){
            System.out.print(imprimir);
        }
        else{
            //es asíncrono y tiene que guardar en el archivo especificado

            File TextFile = new File(Shell.debug_path);
            FileWriter TextOut;
            try {
                TextOut = new FileWriter(TextFile, true);
                TextOut.write(imprimir+"\n");
                TextOut.close();
            } catch (IOException ex) {
                Imprimir_errores("error al guardar el log de io");
            }
        }
    }

    /***********************************************
     * Método: escribir_archivo
     * Uso: escribe en un archivo la cadena a imprimir
     *      en un archivo descrito por el path
     * @param path del archivo a escribir
     * @param imprimir. El contenido que se imprime en el archivo
     * @return boolean diciendo si se escribió el archivo correctamente
    ***********************************************/
    private boolean escribir_archivo(String imprimir) {

        try{
            //System.out.println("imprimir: "+imprimir);
            out.write(imprimir);

        }catch(Exception exp){
            this.Imprimir_errores("Error al guardar archivo\n");
            return false;
        }
        return true;

    }

    /***********************************************
     * Método: escribir_io_log
     * Uso: escribe el log con las peteciones de abrir y cerrar archivos
     * @param imprimir contiene lo que se desa imprimir
     * @return boolean diciendo si se guardó el archivo correctamente
    ***********************************************/
    private boolean escribir_io_log(String imprimir) {

        File TextFile = new File(Shell.path_io_log);
        FileWriter TextOut;
        try {
            TextOut = new FileWriter(TextFile, true);
            TextOut.write(imprimir+"\n");
            TextOut.close();
        } catch (IOException ex) {
            Imprimir_errores("error al guardar el log de io");
            return false;
        }
        return true;

    }
    /***********************************************
     * Método: escribir_io_log
     * Uso: escribe el log con las peteciones de abrir y cerrar archivos
     * @param imprimir contiene lo que se desa imprimir
     * @return boolean diciendo si se guardó el archivo correctamente
    ***********************************************/
    private boolean escribir_ps_log(String imprimir) {

        File TextFile = new File(Shell.path_ps_log);
        FileWriter TextOut;
        try {
            TextOut = new FileWriter(TextFile, true);
            TextOut.write(imprimir+"\n");
            TextOut.close();
        } catch (IOException ex) {
            Imprimir_errores("error al guardar el log de ps");
            return false;
        }
        return true;

    }

}
